package com.zkh.myutils.io.file;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import com.zkh.myutils.code.Charsets;
import com.zkh.myutils.io.IOUtils;
import com.zkh.myutils.io.log.Logger;
import com.zkh.myutils.utils.ArrayUtils;
import com.zkh.myutils.utils.Assert;
import com.zkh.myutils.utils.StringUtils;
import com.zkh.myutils.utils.UtilsException;

public class FileUtils {
	
	/**
	 * 读取文件数据，默认编码UTF-8。（主要处理用记事本能够正常打开的文件）
	 * @param filepath 文件绝对路径
	 * @param reader 每行数据处理器
	 */
	public static void read(String filepath, BiConsumer<Integer, String> reader){
		read(filepath, Charsets.UTF_8, reader);
	}
	
	/**
	 * 读取文件数据（主要处理用记事本能够正常打开的文件）
	 * @param filepath 文件绝对路径
	 * @param charset 编码方式，可使用常量 <i><b>Charsets.编码</b></i> 获取
	 * @param reader 每行数据处理器
	 */
	public static void read(String filepath, Charset charset, BiConsumer<Integer, String> reader){
		try(InputStream is = new FileInputStream(filepath)) {
			read(is, charset, reader);
		} catch (IOException e) {
			throw new UtilsException(e);
		}
	}
	
	/**
	 * 读取文件数据（主要处理用记事本能够正常打开的文件）
	 * @param inputStream 输入流
	 * @param reader 每行数据处理器
	 */
	public static void read(InputStream inputStream, BiConsumer<Integer, String> reader) {
		read(inputStream, Charsets.UTF_8, reader);
	}
	
	/**
	 * 读取文件数据（主要处理用记事本能够正常打开的文件）
	 * @param is 输入流
	 * @param charset 编码方式，可使用常量 <i><b>Charsets.编码</b></i> 获取
	 * @param reader 每行数据处理器
	 */
	public static void read(InputStream inputStream, Charset charset, BiConsumer<Integer, String> reader) {
		//读取文件
		try(InputStreamReader isr = new InputStreamReader(inputStream, charset);
			BufferedReader br = new BufferedReader(isr)){
			//行记录
			int count = 0;
			//临时数据
			String tmp = null;
			//处理文件
			while((tmp=br.readLine())!=null){
				reader.accept(++count, tmp);
			}
		}catch (IOException e) {
			//打印异常
			Logger.error(e);
			//抛出异常
			throw new UtilsException("文件数据读取异常");
		}
	}
	
	/**
	 * 读取所有文件内容，默认UTF-8编码（主要处理用记事本能够正常打开的文件）
	 * @param filepath 文件路径
	 * @return
	 */
	public static String readAll(String filepath) {
		return readAll(filepath, Charsets.UTF_8);
	}
	
	/**
	 * 读取所有文件内容（主要处理用记事本能够正常打开的文件）
	 * @param filepath 文件路径
	 * @param charset 读取编码，可使用常量 <i><b>Charsets.编码</b></i> 获取
	 * @return
	 */
	public static String readAll(String filepath, Charset charset) {
		try(InputStream is = new FileInputStream(filepath)) {
			return readAll(is, charset);
		} catch (IOException e) {
			throw new UtilsException(e);
		}
	}
	
	/**
	 * 读取所有文件内容，默认UTF-8编码（主要处理用记事本能够正常打开的文件）
	 * @param inputStream 输入流
	 * @return
	 */
	public static String readAll(InputStream inputStream) {
		return readAll(inputStream, Charsets.UTF_8);
	}
	
	/**
	 * 读取所有文件内容（主要处理用记事本能够正常打开的文件）
	 * @param inputStream 输入流
	 * @param charset 读取编码，可使用常量 <i><b>Charsets.编码</b></i> 获取
	 * @return
	 */
	public static String readAll(InputStream inputStream, Charset charset) {
		try {
			//返回文本
			return new String(IOUtils.toByteArray(inputStream), charset);
		}catch(Exception e) {
			throw new UtilsException(e);
		}
	}
	
	/**
	 * 将文本内容写入文件（覆盖）
	 * @param file 指定文件
	 * @param content 待写入文件的文本内容
	 */
	public static void writeContent(File file, String content) {
		writeContent(file, content, false);
	}
	
	/**
	 * 将文本内容写入文件
	 * @param file 指定文件
	 * @param content 待写入文件的文本内容
	 * @param append 是否追加，true追加，false覆盖
	 */
	public static void writeContent(File file, String content, boolean append) {
		//写入流
		try(OutputStream os = new FileOutputStream(file, append)) {
			//写入数据
			os.write(content.getBytes(Charsets.UTF_8));
		}catch(IOException e) {
			throw new UtilsException(e);
		}
	}
	
	/**
	 * 获取文件的扩展名
	 * @param filename 文件名称，可包含路径
	 * @return 扩展名，不含.
	 */
	public static String getSuffix(String filename){
		//获取文件名
		filename = getFilename(filename);
		//检测后缀
		int i = filename.lastIndexOf('.');
		//检测是否有后缀名
		return i>-1 && !filename.endsWith(".") ? filename.substring(i+1) : "";
	}
	
	/**
	 * 从文件路径中获取文件名
	 * @param filepath 文件路径
	 * @return
	 */
	public static String getFilename(String filepath) {
		//处理文件名，将所有“\”转为“/”
		filepath = filepath.replace('\\', '/');
		//检查参数
		if(filepath.endsWith("/")) {
			throw new IllegalArgumentException("你传入的不是文件路径！");
		}
		//获取目录分隔符
		int sep = filepath.lastIndexOf('/');
		//获取
		return sep==-1 ? filepath : filepath.substring(sep+1);
	}
	
	/**
	 * 获取文件的扩展名
	 * @param filename  文件名称，可包含路径
	 * @param withDot 是否带点，true是
	 * @return 扩展名
	 */
	public static String getSuffix(String filename, boolean withDot){
		//获取不带点的扩展名
		String suffix = getSuffix(filename);
		//判断是否带点
		if(withDot && !suffix.isEmpty()){
			suffix = "."+suffix;
		}
		//返回扩展名
		return suffix;
	}
	
	/**
	 * 将parent目录下的所有文件和目录复制到指定目录
	 * @param parent 目录，复制该目录下的文件或目录
	 * @param targetDir 目标目录
	 */
	public static void copyChildren(File parent, File targetDir) {
		copyChildren(parent, targetDir, file->true);
	}
	
	/**
	 * 将parent目录下的所有文件和目录复制到指定目录
	 * @param parent 目录，复制该目录下的文件或目录
	 * @param targetDir 目标目录
	 * @param filter 过滤方法，返回true，则处理，返回false，则跳过
	 */
	public static void copyChildren(File parent, File targetDir, Predicate<File> filter) {
		Assert.doIfNotEmpty(parent.list(), filenames->{
			//遍历处理
			for(String filename: filenames) {
				//当前文件
				File f = new File(parent, filename);
				//过滤器验证
				if(filter.test(f)) {
					copy(f, targetDir, filter);
				}
			}
		});
	}
	
	/**
	 * 将指定文件或目录复制到指定目录下
	 * @param source 要复制的源文件或文件夹
	 * @param targetDir 目标目录
	 */
	public static void copy(File source, File targetDir) {
		copy(source, targetDir, file->true);
	}
	
	/**
	 * 将指定文件或目录复制到指定目录下
	 * @param source 要复制的源文件或文件夹
	 * @param targetDir 目标目录
	 * @param filter 过滤方法，返回true，则处理，返回false，则跳过
	 */
	public static void copy(File source, File targetDir, Predicate<File> filter) {
		//源对象验证
		if(filter.test(source)) {
			//如果是目录
			if(source.isDirectory()) {
				//目标目录
				File tgtDir = new File(targetDir, source.getName());
				//创建目录
				if(!tgtDir.isDirectory()) {
					tgtDir.mkdir();
				}
				//复制
				copyChildren(source, tgtDir, filter);
			}else {
				//复制
				copyFile(source, targetDir);
			}
		}
	}
	
	/**
	 * 复制文件
	 * @param source 源文件
	 * @param target 目标文件或目录
	 */
	public static void copyFile(File source, File target) {
		if(!source.isFile()) {
			throw new UtilsException("你复制的不是文件");
		}
		//目标文件（如果目标是目录，则使用原文件名；文件不存在自动创建）
		target = createFile(target.isDirectory() ? new File(target, source.getName()) : target);
		//流句柄
		try(InputStream inputStream = new FileInputStream(source);
			OutputStream outputStream = new FileOutputStream(target)) {
			//临时变量
			byte[] bytes = new byte[1024];int len = 0;
			//开始读写文件
			while((len=inputStream.read(bytes))>0) {
				outputStream.write(bytes, 0, len);
			}
			outputStream.flush();
		}catch(Exception e) {
			throw new UtilsException(e);
		}
	}
	
	/**
	 * 将文件的InputStream保存到指定目录下
	 * @param 上传文件的InputStream流
	 * @param path 保存的目录，磁盘绝对位置
	 * @param filename 保存的文件名
	 */
	public static void saveInputStreamTo(InputStream is, String path, String filename) {
		saveInputStreamTo(is, new File(path, filename));
	}

	/**
	 * 将文件的InputStream保存到指定文件
	 * @param 上传文件的InputStream流
	 * @param file 保存文件
	 */
	public static void saveInputStreamTo(InputStream is, File file){
		//创建输出流
		try(FileOutputStream os = new FileOutputStream(file)) {
			//创建字节数组
			byte[] bytes = new byte[1024];
			//数据长度
			int len = 0;
			//循环遍历
			while((len=is.read(bytes))>0){
				os.write(bytes, 0, len);
			}
		}catch(IOException e){
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 创建目录（如果不存在）
	 * @param dirs 目录
	 */
	public static File mkdirs(File dirs) {
		if(!dirs.isDirectory()) {
			dirs.mkdirs();
		}
		return dirs;
	}
	
	/**
	 * 创建文件（如果不存在）
	 * @param file
	 */
	public static File createFile(File file) {
		try {
			if(!file.isFile()) {
				file.createNewFile();
			}
			return file;
		}catch(IOException e) {
			throw new UtilsException("创建文件时发生异常", e);
		}
	}
	
	/**
	 * 删除指定目录下的文件或目录
	 * @param parent 指定父级目录
	 */
	public static void deleteChildren(File parent) {
		deleteChildren(parent, file->true);
	}
	
	/**
	 * 删除指定目录下的文件或目录
	 * @param parent 指定父级目录
	 * @param predicate 自定义删除逻辑，返回true执行删除，返回false跳过
	 */
	public static void deleteChildren(File parent, Predicate<File> filter) {
		if(!parent.isDirectory()) {
			throw new UtilsException("传入的参数不是一个目录");
		}
		//所有文件或目录
		Assert.doIfNotEmpty(parent.list(), fileList->{
			for(String file: fileList) {
				delete(new File(parent, file), filter);
			}
		});
	}
	
	/**
	 * 删除文件或目录
	 * @param file 要删除的文件或目录
	 */
	public static void delete(File file) {
		delete(file, f->true);
	}
	
	/**
	 * 删除文件或目录。<br>注：如果目录下因为删除规则没有删除完文件，删除目录时是会失败的，因为程序只能删除空目录。
	 * @param file 要删除的文件或目录
	 * @param filter 自定义删除逻辑，返回true执行删除，返回false跳过
	 */
	public static void delete(File file, Predicate<File> filter) {
		//满足条件，执行删除
		if(filter.test(file) && file.exists()) {
			//文件
			if(file.isFile()) {
				file.delete();
			}else {
				//遍历子目录和文件，递归删除
				Assert.doIfNotEmpty(file.list(), list->{
					for(String child: list) {
						delete(new File(file, child), filter);
					}
				});
				//删除目录
				file.delete();
			}
		}
	}
	
	/**
	 * 将文件或目录压缩为zip格式
	 * @param sourceFile 要压缩的文件或目录
	 * @param path 要将文件或目录压缩到的位置
	 */
	public static void compressToZip(String sourceFile, String path) throws IOException {
		compressToZip(sourceFile, path, null);
	}
	
	/**
	 * 将文件或目录压缩为zip格式
	 * @param sourceFile 要压缩的文件或目录
	 * @param path 要将文件或目录压缩到的位置
	 * @param filename 指定保存文件名称（含后缀）。为空时，默认为“目录或文件名称.zip”
	 */
	public static void compressToZip(String sourceFile, String path, String filename) throws IOException{
		//压缩
		zipFile(new File(path, StringUtils.isEmpty(filename) ? new File(sourceFile).getName()+".zip" : filename), ()->Arrays.asList(sourceFile));
	}
	
	/**
	 * 将指定目录下的所有文件和子目录压缩为zip格式
	 * @param parentDirectory 父目录，将压缩该目录下的所有文件和子目录，文件名为“父目录.zip”
	 * @param path 压缩文件存放路径
	 */
	public static void compressChildrenToZip(String parentDirectory, String path) throws IOException{
		compressChildrenToZip(parentDirectory, path, null);
	}
	
	/**
	 * 将指定目录下的所有文件和子目录压缩为zip格式
	 * @param parentDirectory 父目录，将压缩该目录下的所有文件和子目录
	 * @param path 压缩文件存放路径
	 * @param filename 指定压缩文件名（含后缀）。为空时，默认为“父目录.zip”
	 */
	public static void compressChildrenToZip(String parentDirectory, String path, String filename) throws IOException{
		//实例化目录
		File parent = new File(parentDirectory);
		//非目录
		if(!parent.isDirectory()) {
			throw new UtilsException("参数parentDirectory必须是目录");
		}
		//格式化目录
		String root = parent.getAbsolutePath();
		//处理文件名
		if(StringUtils.isEmpty(filename)) {
			filename = parent.getName()+".zip";
		}
		//压缩
		zipFile(new File(path, filename), ()->Arrays.asList(ArrayUtils.map(parent.list(), f->root+"/"+f)));
	}
	
	/**
	 * 压缩文件或目录
	 * @param target zip文件File对象
	 * @param supplier 要压缩的文件或目录列表
	 */
	private static <T> void zipFile(File target, Supplier<List<String>> supplier) throws IOException {
		//创建流对象
		try(FileOutputStream fos =  new FileOutputStream(target);
			ZipOutputStream zos = new ZipOutputStream(fos)) {
			//遍历
			for(String fp: supplier.get()) {
				//源对象
				File source = new File(fp);
				//调用压缩方法
				zipFile(source, zos, source.getName());
			}
		}
	}
	
	/**
	 * 创建压缩文件，内部使用
	 * @param source 待压缩目录或文件
	 * @param target zip输出流
	 * @param base 相对路径
	 */
    private static void zipFile(File source, ZipOutputStream target, String base) throws IOException {
    	//如果是目录，依次压缩每个子项
    	if(source.isDirectory()) {
    		//获取源目录下的所有文件对象
    		String[] files = source.list();
    		//不为空
    		if(files!=null) {
    			for(String file: files) {
    				zipFile(new File(source, file), target, base+"/"+file);
    			}
    		}
        }else{
        	target.putNextEntry(new ZipEntry(base));
        	//输出流
        	try(FileInputStream fis = new FileInputStream(source)){
            	//写入数据流
            	IOUtils.copyStreamBuffer(fis, target);
        	}
        }
	}
}
